Skip to content

[megatron] feat: migrate fused logprob/entropy from GPTModel.forward monkey-patch to Megatron output_processor hook#6933

Open
chengcuiping wants to merge 3 commits into
verl-project:mainfrom
chengcuiping:feat/fused-output-processor-hook
Open

[megatron] feat: migrate fused logprob/entropy from GPTModel.forward monkey-patch to Megatron output_processor hook#6933
chengcuiping wants to merge 3 commits into
verl-project:mainfrom
chengcuiping:feat/fused-output-processor-hook

Conversation

@chengcuiping

@chengcuiping chengcuiping commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

verl/models/mcore/model_forward_fused.py currently replaces the full GPTModel.forward through _fused_GPTModel_forward so the training loop can pass temperature into Megatron's _postprocess boundary, where linear_cross_entropy computes fused log-probabilities and entropy.

Megatron-LM PR #4686 added the native output_processor / output_processor_context extension point at that boundary. This PR uses that hook for verl's fused Megatron log-probability and entropy path while retaining the legacy monkey-patch as a compatibility fallback.

The fused forward mode is decided once at model build time:

  • auto is the default. It uses the native hook when the actual bound Megatron model forward supports both output_processor and output_processor_context, and otherwise falls back to the legacy patch.
  • hook requires the native hook and fails fast during model construction if the required contract is unavailable.
  • legacy forces the existing monkey-patched GPTModel.forward path.

The selected mode is stored on the real Megatron model instance. Runtime forward calls read that fixed mode and do not repeatedly inspect the environment.

The live Megatron engine path is covered through fused_forward_model_engine. The legacy fused_forward_model caller is kept compatible as well. Megatron's build_schedule_plan() / 1F1B postprocess integration is outside this PR and remains part of the broader upstream work tracked in NVIDIA/Megatron-LM#4590.

Checklist Before Starting

Test

Focused tests added in this revision

pytest -s -x tests/models/test_model_forward_fused.py

Result:

6 passed

The focused tests cover:

  • auto selecting the hook path when the bound model forward supports the full hook contract.
  • auto falling back to the legacy monkey-patch when the hook contract is unavailable.
  • forced hook mode failing fast during model construction when required parameters are missing.
  • invalid environment values raising ValueError.
  • runtime callers using the mode fixed during model construction rather than rereading the environment.
  • hook mode passing temperature through output_processor_context without forwarding temperature= to native Megatron forward.
  • legacy mode continuing to pass temperature= to the patched forward.
  • sequence-parallel gather occurring before the fused linear cross-entropy call.
  • tied and untied output-weight resolution.
  • patch and unpatch behavior remaining idempotent.

Pre-submit checks:

pre-commit run --all-files

Result:

passed
git diff upstream/main...HEAD --check

Result:

clean

Previously completed validation

The numerical and live integration validation below was completed before the latest rebase and was not rerun in full for this revision.

Environment used for the same-batch numerical validation:

Megatron-Core: 0.18.0+97f3bce88
PyTorch: 2.9.1+cu128
Transformer Engine: 2.10
flash-attn: 2.8.3
dtype: bf16
Scenario Coverage Result
Synthetic same-batch A/B TP=1/2, tied and untied embeddings, both output-weight branches, sequence-parallel gather log_probs / entropy max_abs=0
Real model-build regression manual wrapping versus the real model-build chain bit-identical
Real training-shape batch variable-length padding, THD padding_causal, GRPO advantages, including the covered pg_loss path max_abs=0
Environment with vLLM installed regression check for dependency contamination bit-identical
Live Megatron engine form real GSM8K tokens, nested/THD inputs, TP=2 tied embeddings and TP=1 tied embeddings, fused_forward_model_engine with build-time patch selection log_probs / entropy max_abs=0

A separate 4×A100 five-step GRPO smoke previously exercised:

  • Megatron engine with TP=2 and PP=1.
  • vLLM rollout.
  • fused log-probability and entropy computation.
  • parameter, gradient, and optimizer offload.
  • optimizer updates.
  • rollout-to-actor weight resharding.
  • distributed checkpoint save/load.

Both the hook and legacy paths completed that five-step integration run. The two independent runs saw different sampled trajectories, so per-step metrics were not expected to be identical; exact numerical equivalence was established by the same-weight, same-batch A/B validation above.

Forward memory usage was also measured as identical between the hook and legacy paths (Δ = 0).

Rebased local smoke limitation

A rebased local smoke was attempted, but the checkout runtime did not match the project's full CUDA image dependencies and the run stopped before Megatron model construction. The output_processor path was therefore not reached in that local run.

The focused tests above pass, and the previously completed same-batch numerical validation and live five-step integration validation remain passing. The official project CI environment will provide the final rebased integration check.

Known limitation

The FP8 padding path was not exercised:

use_fp8_padding
config.fp8 in {e4m3, hybrid}

All numerical validation reported above used bf16. The callback itself does not introduce FP8-specific logic, but this PR does not claim coverage of the FP8 input-padding path.

API and Usage Example

# Recommended default: use the native hook when supported,
# otherwise fall back to the legacy forward patch.
VERL_FUSED_USE_OP_HOOK=auto

# Require the native hook and fail fast if unsupported.
VERL_FUSED_USE_OP_HOOK=hook

# Force the legacy GPTModel.forward patch.
VERL_FUSED_USE_OP_HOOK=legacy

Leaving VERL_FUSED_USE_OP_HOOK unset or setting it to an empty value is equivalent to auto.

Accepted aliases are:

auto:   unset, "", auto
hook:   1, true, yes, hook
legacy: 0, false, no, legacy

Any other value raises ValueError during model construction.

Design & Code Changes

  • Parse VERL_FUSED_USE_OP_HOOK once during patch_fused_forward.
  • Resolve the actual underlying Megatron GPT model before capability probing.
  • Probe the actual bound model.forward signature for both output_processor and output_processor_context.
  • Store the selected mode on the real model instance.
  • Have both fused forward callers read the stored mode instead of rereading the process environment.
  • Keep hook and legacy selection stable even if the environment variable changes after model construction.
  • In hook mode, leave native GPTModel.forward untouched and do not create forward_backup.
  • In legacy mode, retain the existing monkey-patch and forward_backup compatibility path.
  • Keep repeated patch and unpatch calls safe and idempotent.
  • Raise a clear RuntimeError when forced hook mode is requested but the bound forward lacks the required hook contract.
  • Remove the redundant output-weight assertion identified during review.

Two correctness details are intentional:

  1. Megatron passes output_processor_context to the callback using the keyword context. The callback parameter is therefore named context, while the native forward call correctly uses output_processor_context=....

  2. The hook's output-weight contract differs from the legacy copied forward:

    • tied embeddings provide the shared weight through output_weight;
    • untied embeddings provide output_weight=None, and the callback uses output_layer.weight.

The callback therefore resolves the fused-kernel weight with:

weight = output_weight if output_weight is not None else output_layer.weight

Under sequence parallelism, the callback gathers hidden_states before invoking linear_cross_entropy, matching the established legacy behavior.

Checklist Before Submitting

  • I have read the contribution guide.
  • pre-commit run --all-files passed.
  • Focused tests were added.
  • The CI workflow was updated to run the focused tests.
  • Separate documentation was not added; the environment-variable API is documented in the usage example above.
  • CI has not yet been requested in the project's ci-request channel.
  • Not applicable: this PR does not modify the recipe submodule.

AI assistance disclosure: OpenAI Codex assisted with implementation and test execution; I reviewed the resulting changes and test outputs.

@CLAassistant

CLAassistant commented Jul 6, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an alternative hook-based path (output_processor) in verl/models/mcore/model_forward_fused.py for Megatron-Core, which avoids monkey-patching GPTModel.forward by passing a callback and context directly to the native forward method. The hook path is controlled via the VERL_FUSED_USE_OP_HOOK environment variable and includes capability checks. The feedback suggests removing a redundant assertion checking if weight is None in fused_output_processor, as the subsequent call to linear_cross_entropy would naturally fail with a clear error.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread verl/models/mcore/model_forward_fused.py Outdated
@chengcuiping

Copy link
Copy Markdown
Contributor Author

Quick follow-up: besides the offline bit-identical A/B in the PR description, I ran a small live end-to-end smoke on Megatron-Core 0.18 to make sure the hook path survives the full RL loop, not just an isolated forward.

Setup was a dense Qwen2.5-0.5B on 4×A100, megatron engine (v1) with TP=2/PP=1, use_fused_kernels=True, full param/grad/optimizer offload, vLLM rollout, GRPO on gsm8k, 5 steps with a checkpoint at step 4. I ran it twice — once with VERL_FUSED_USE_OP_HOOK=0 (legacy patch) and once with =1 (the hook) — so the whole loop gets exercised both ways: rollout → weight resharding → fused logprob/entropy → optimizer update → distributed checkpoint.

Both paths completed all 5 steps and saved checkpoints cleanly, with no NaN/inf and comparable, sane metrics (entropy ~0.43–0.66, training log-ppl ~0.40–0.62 on both). The three integration points I was worried about all held up under the hook: rollout↔actor resharding (actor/rollout prob correlation ~0.9996 each step), offload interacting with the hook path, and dist-checkpoint save/load.

I also added a one-off build-time check to confirm the hook is genuinely what's running: under VERL_FUSED_USE_OP_HOOK=1 the GPTModel has no forward_backup on any rank (i.e. patch_fused_forward was a no-op and the native forward is untouched), while under =0 it does. So the live run really is going through output_processor, not the monkey-patch.

One honest caveat: the per-step metrics differ slightly between the two runs, but that's rollout non-reproducibility (vLLM across independent processes samples different trajectories), not the hook — the two runs simply don't see the same data. The exact numerical equivalence is what the offline A/B in the PR description covers (max_abs=0 on identical weights + batch); this live run is about integration, not re-proving the numerics.

Heads-up on one prerequisite unrelated to this PR: bringing up the megatron backend on Megatron-Core 0.18 currently also needs mbridge patched, because mbridge 0.15.1 (latest) still references the removed ModelType.encoder_and_decoder and crashes at model build — upstream of any forward, so it's orthogonal to this change. Filed separately (mbridge: ISEEKYAN/mbridge#155, verl dependency note: #6936).

chengcuiping and others added 3 commits July 10, 2026 16:17
…cessor hook

Replace the full GPTModel.forward monkey-patch (which existed only to pass
`temperature` into _postprocess) with Megatron PR verl-project#4686's `output_processor`
hook (core >= 0.18). Gated behind VERL_FUSED_USE_OP_HOOK (default 0 =
legacy path, byte-identical to today).

- Add use_output_processor_hook(), FusedOutputProcessorContext, and the
  fused_output_processor callback (SP gather + linear_cross_entropy at the
  _postprocess boundary -> CausalLMOutputForPPO).
- patch_fused_forward / unpatch_fused_forward: no-op in hook mode.
- fused_forward_model (legacy) and fused_forward_model_engine (live engine
  path): pass temperature via output_processor_context instead of temperature=.
- Fail fast at build time via signature introspection when hook mode is
  requested but GPTModel.forward lacks `output_processor`.

Downstream A/B (hook vs patch) is bit-identical (log_probs/entropy max_abs=0)
across TP=1/2, tied/non-tied, SP gather, real THD training-shape batches, and
the engine live path; forward memory delta is 0. Refs NVIDIA/Megatron-LM#4590.
Assisted-by: OpenAI Codex

Signed-off-by: chengcuiping <96756894+chengcuiping@users.noreply.github.com>
Assisted-by: OpenAI Codex

Signed-off-by: chengcuiping <96756894+chengcuiping@users.noreply.github.com>
@chengcuiping chengcuiping force-pushed the feat/fused-output-processor-hook branch from d330a7f to f49866a Compare July 10, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants